﻿using Newtonsoft.Json;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Users;
using Volo.Abp;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.ExceptionHandling;
using Volo.Abp.Authorization;
using Volo.Abp.ExceptionHandling;
using Volo.Abp.Http;
using Volo.Abp.Json;
using Microsoft.Net.Http.Headers;

namespace My.WebApi.Middlewares
{
    public class UserLoginStatusMiddleware : IMiddleware, IScopedDependency
    {
        private readonly ICurrentUser _currentUser;
        private readonly IDistributedCache _distributedCache;

        public UserLoginStatusMiddleware(
            IDistributedCache distributedCache,
            ICurrentUser currentUser)
        {
            _distributedCache = distributedCache;
            _currentUser = currentUser;
        }

        public async Task InvokeAsync(HttpContext context, RequestDelegate next)
        {
            try
            {
                var uniqueId = context.Request.Headers["UniqueId"].ToString();
                if (uniqueId.IsNullOrWhiteSpace())
                {
                    throw new UserFriendlyException(message: "用户已经在别处登录", code: "50000");
                }
                else
                {
                    var loginCacheDtoStr = await _distributedCache.GetStringAsync($"Login:{_currentUser.Id}");
                    if (!loginCacheDtoStr.IsNullOrWhiteSpace())
                    {
                        var loginCacheDto = JsonConvert.DeserializeObject<LoginCacheDto>(loginCacheDtoStr);
                        if (loginCacheDto?.UniqueId.ToString() != uniqueId)
                        {
                            throw new UserFriendlyException(message: "用户已经在别处登录", code: "50000");
                        }
                    }
                    await next(context);
                }
            }
            catch (Exception ex)
            {
                await HandleAndWrapException(context, ex);
            }
        }

        private async Task HandleAndWrapException(HttpContext httpContext, Exception exception)
        {
            await httpContext
                .RequestServices
                .GetRequiredService<IExceptionNotifier>()
                .NotifyAsync(
                    new ExceptionNotificationContext(exception)
                );

            if (exception is AbpAuthorizationException)
            {
                await httpContext.RequestServices.GetRequiredService<IAbpAuthorizationExceptionHandler>()
                    .HandleAsync(exception.As<AbpAuthorizationException>(), httpContext);
            }
            else
            {
                var errorInfoConverter = httpContext.RequestServices.GetRequiredService<IExceptionToErrorInfoConverter>();
                var statusCodeFinder = httpContext.RequestServices.GetRequiredService<IHttpExceptionStatusCodeFinder>();
                var jsonSerializer = httpContext.RequestServices.GetRequiredService<IJsonSerializer>();
                var exceptionHandlingOptions = httpContext.RequestServices.GetRequiredService<IOptions<AbpExceptionHandlingOptions>>().Value;

                httpContext.Response.Clear();
                httpContext.Response.StatusCode = 200;
                httpContext.Response.OnStarting(ClearCacheHeaders, httpContext.Response);
                httpContext.Response.Headers.Add(AbpHttpConsts.AbpErrorFormat, "true");
                httpContext.Response.Headers.Add("Content-Type", "application/json");

                await httpContext.Response.WriteAsync(
                    jsonSerializer.Serialize(
                        new RemoteServiceErrorResponse(
                            errorInfoConverter.Convert(exception, options =>
                            {
                                options.SendExceptionsDetailsToClients = exceptionHandlingOptions.SendExceptionsDetailsToClients;
                                options.SendStackTraceToClients = exceptionHandlingOptions.SendStackTraceToClients;
                            })
                        )
                    )
                );
            }
        }

        private Task ClearCacheHeaders(object state)
        {
            var response = (HttpResponse)state;

            response.Headers[HeaderNames.CacheControl] = "no-cache";
            response.Headers[HeaderNames.Pragma] = "no-cache";
            response.Headers[HeaderNames.Expires] = "-1";
            response.Headers.Remove(HeaderNames.ETag);

            return Task.CompletedTask;
        }
    }

    public class LoginCacheDto
    {
        public Guid? UserId { get; set; }

        public string UserName { get; set; }

        public Guid UniqueId { get; set; }
    }
}
